﻿Shader "Tiny Shader/Toon/Water"
{
    Properties
    {
        // 最浅处水面颜色
        _DepthGradientShallow ("Depth Gradient Shallow", Color) = (0.325, 0.807, 0.971, 0.725)
        // 最深处水面颜色
        _DepthGradientDeep ("Depth Gradient Deep", Color) = (0.086, 0.407, 1, 0.749)
        // 超过这个深度后的水面颜色不再变化（等同于最深处的睡眠颜色）
        _DepthMaxDistance ("Depth Maximum Distance", Float) = 1

        // 噪声纹理，用于模拟水波高亮的颜色
        _SurfaceNoise ("Surface Noise", 2D) = "white" {}
        // 噪声纹理的阈值，用于控制噪声纹理的采样值是否显示到水面上
        _SurfaceNoiseCutoff ("Surface Noise Cutoff", Range(0, 1)) = 0.777

        // 控制多少深度的水面会显示泡沫
        _FoamDistance ("Foam Distance", Float) = 0.4
        // 泡沫最大深度，作为法线点乘后的参考值，沿用原来的泡沫深度逻辑
        _FoamMaxDistance ("Foam Maximum Distance", Float) = 0.4
        // 泡沫最小深度，作为法线点乘后的参考值，沿用原来的泡沫深度逻辑
        _FoamMinDistance ("Foam Minumum Distance", Float) = 0.04

        // 用于UV动画的速度
        _SurfaceNoiseScroll ("Surface Noise Scroll Amount", Vector) = (0.03, 0.03, 0, 0)
        // 扰动纹理
        _SurfaceDistortion ("Surface Distortion", 2D) = "white" {}
        // 控制扰动的强度
        _SurfaceDistortionAmount ("Surface Distortion Amount", Range(0, 1)) = 0.27
    }

    SubShader
    {
        Tags
        {
            "Queue" = "Transparent"
        }

        Pass
        {
            Blend SrcAlpha OneMinusSrcAlpha
            // 关闭深度写入，一方面是透明度混合需要，让水体看起来更自然，
            // 另一方面可以保证相机的深度纹理只记录水面下的物体距离相机的深度
            ZWrite Off

            CGPROGRAM
            // 用于泡沫抗锯齿
            #define SMOOTHSTEP_AA 0.05

            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float4 uv : TEXCOORD0;
                float3 normal : NORMAL;
            };

            struct v2f
            {
                float4 vertex : SV_POSITION;
                // 观察空间的法线方向
                float3 viewNormal : NORMAL;
                // 片元在屏幕空间的坐标
                float4 screenPosition : TEXCOORD0;
                // 噪声纹理UV
                float2 noiseUV : TEXCOORD1;
                // 扰动纹理UV
                float2 distortUV : TEXCOORD2;
            };

            // 水面深浅颜色变量
            float4 _DepthGradientShallow;
            float4 _DepthGradientDeep;
            float _DepthMaxDistance;
            // 相机深度纹理，用于计算水面到水底的深度
            sampler2D _CameraDepthTexture;

            // 水波相关变量
            sampler2D _SurfaceNoise;
            // ST后缀用于结合纹理的偏移量和缩放，得到正确的纹理UV
            float4 _SurfaceNoise_ST;
            float _SurfaceNoiseCutoff;

            // 泡沫相关变量
            float _FoamDistance;
            float _FoamMaxDistance;
            float _FoamMinDistance;
            // 全局变量，在C#脚本中通过相机渲染物体得到的法线数值颜色的问题
            sampler2D _CameraNormalsTexture;

            // 动画相关
            float2 _SurfaceNoiseScroll;
            sampler2D _SurfaceDistortion;
            float4 _SurfaceDistortion_ST;
            float _SurfaceDistortionAmount;

            float4 alphaBlend (float4 top, float4 bottom)
            {
                float3 color = (top.rgb * top.a) + (bottom.rgb * (1 - top.a));
                float alpha = top.a + bottom.a * (1 - top.a);
                return float4(color, alpha);
            }

            v2f vert (appdata v)
            {
                v2f o;

                o.vertex = UnityObjectToClipPos(v.vertex);
                // 使用内置函数：ComputeScreenPos，
                // 计算当前顶点在屏幕空间的齐次坐标（传参是裁剪空间下的坐标）
                // 得到的x、y分量范围是：[0, w]
                o.screenPosition = ComputeScreenPos(o.vertex);
                // COMPUTE_VIEW_NORMAL：内置宏，在UnityCG.cginc定义，用于计算出观察空间下的法线
                o.viewNormal = COMPUTE_VIEW_NORMAL;
                // 噪声UV
                o.noiseUV = TRANSFORM_TEX(v.uv, _SurfaceNoise);
                // 扰动UV
                o.distortUV = TRANSFORM_TEX(v.uv, _SurfaceDistortion);

                return o;
            }

            float4 frag (v2f i) : SV_Target
            {
                // ----------------------水面颜色--------------------------
                // 对相机深度纹理进行采样，得到水面下方物体表面距离相机的深度
                // UNITY_PROJ_COORD：处理平台化差异，大多数平台下直接返回传入的参数；某些平台值返回参数的xyw分量
                // tex2Dproj：类似tex2D纹理采样函数，只是采样前多了一步操作，将纹理坐标x、y分量除以w分量（pos.x / pos.w）
                float existingDepth01 = tex2Dproj(_CameraDepthTexture, UNITY_PROJ_COORD(i.screenPosition)).r;
                // 相机深度纹理采样得到的深度值可能是非线性的（源于透视投影使用的裁剪矩阵）
                // 需要使用内置函数：LinearEyeDepth，将非线性的深度值转换到观察空间的深度值
                // 相关知识点详见《Unity Shader入门精要》第13章第1节内容《获取深度和法线纹理》
                float existingDepthLinear = LinearEyeDepth(existingDepth01);

                // screenPosition中的w分量记录了当前像素到相机的距离，
                // 通过两个深度值相减，可得到视角方向水面到水底的距离
                float depthDifference = existingDepthLinear - i.screenPosition.w;
                // 得到归一化后的水面深度
                float waterDepthDifference01 = saturate(depthDifference / _DepthMaxDistance);
                // 差值得到当前像素水面的颜色
                float4 waterColor = lerp(_DepthGradientShallow, _DepthGradientDeep, waterDepthDifference01);

                // ----------------------动画--------------------------
                // 因为扰动纹理时法线纹理，所以需要解析，然后在于强度控制因子相乘
                float2 distortSample = (tex2D(_SurfaceDistortion, i.distortUV).xy * 2 - 1) * _SurfaceDistortionAmount;
                // 噪声UV经过时间偏移后再基于扰动纹理采样做偏移
                float2 noiseUV = float2(i.noiseUV.x + _Time.y * _SurfaceNoiseScroll.x + distortSample.x, i.noiseUV.y + _Time.y * _SurfaceNoiseScroll.y + distortSample.y);

                // ----------------------水波--------------------------
                // 采样得到噪声纹理的颜色值，使用r通道作为水波的参数
                float surfaceNoiseSample = tex2D(_SurfaceNoise, noiseUV).r;

                // ----------------------泡沫--------------------------
                // 水中物体的法线方向，基于当前像素在屏幕中的位置，映射到法线纹理中，得到对应的法线方向
                float3 existingNormal = tex2Dproj(_CameraNormalsTexture, UNITY_PROJ_COORD(i.screenPosition));
                // 将物体的法线方向和水面的法线方向做点乘
                float3 normalDot = saturate(dot(existingNormal, i.viewNormal));
                // 将点乘结果在泡沫深度最大值和最小值中进行差值
                float foamDistance = lerp(_FoamMaxDistance, _FoamMinDistance, normalDot);
                // 根据水面深度，得到控制泡沫是否显示的归一化参数
                float foamDepthDifference01 = saturate(depthDifference / foamDistance);
                // float foamDepthDifference01 = saturate(depthDifference / _FoamDistance);

                // ------------------------汇总------------------------------
                // 噪声纹理的采样大于阈值，则显示白色（水波的高亮地方）
                // 这里阈值多了一层计算，将阈值乘以泡沫的参数，让有泡沫的地方（foamDepthDifference01较小的地方）呈现出白色
                float surfaceNoiseCutoff = _SurfaceNoiseCutoff * foamDepthDifference01;
                // 使用smoothstep方法，让泡沫到水体之间的过渡柔和些
                float surfaceNoise = smoothstep(surfaceNoiseCutoff - SMOOTHSTEP_AA, surfaceNoiseCutoff + SMOOTHSTEP_AA, surfaceNoiseSample);

                return waterColor + surfaceNoise;
            }
            ENDCG
        }
    }
}